using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace TankWars
{
    public abstract class SpriteAnimation
    {
        private const double D_FRAMES_PER_SECOND = 30;

        private struct Animation
        {
            private bool bRepeatAnimation;
            private int nNumOfRepeatitions;

            public List<Rectangle> lstFrames;

            public bool RepeatAnimation
            {
                get
                {
                    return this.bRepeatAnimation;
                }
            }
            public int NumOfRepeatitions
            {
                get
                {
                    return this.nNumOfRepeatitions;
                }
            }

            public Animation(bool bRepeatAnimation, int nNumOfRepeatitions)
            {
                this.bRepeatAnimation = bRepeatAnimation;
                this.nNumOfRepeatitions = nNumOfRepeatitions;

                this.lstFrames = new List<Rectangle>();
            }
        }

        #region Private Variables
        //Private to ensure that only sprite animation class can draw
        private Texture2D Sprite;

        private Dictionary<string, Animation> dicAnimations = new Dictionary<string, Animation>();
        private int nSelectedFrame = 0;
        private double dPreviousFrameTime = 0;
        private int nRepeatitions = 0;
        private string strCurrentAnimationLabel = "Default";

        private bool bAnimating = false;
        #endregion

        #region Properties
        public bool IsAnimating
        {
            get
            {
                return this.bAnimating;
            }
        }

        /// <summary>
        /// Returns the center of the sprite
        /// </summary>
        public Vector2 Origin
        {
            get
            {
                Vector2 origin = new Vector2();
                origin.X = this.SpriteWidth / 2;
                origin.Y = this.SpriteHeight / 2;

                return origin;
            }
        }

        public Rectangle SpriteRectangle
        {
            get
            {
                return this.dicAnimations[this.strCurrentAnimationLabel].lstFrames[this.nSelectedFrame];
            }
        }

        public int SpriteWidth
        {
            get
            {
                return this.SpriteRectangle.Width;
            }
        }

        public int SpriteHeight
        {
            get
            {
                return this.SpriteRectangle.Height;
            }
        }
        #endregion

        public SpriteAnimation(string strSpriteName, Texture2D spriteTexture, string strFrameXMLFile)
        {
            this.Sprite = spriteTexture;

            this.ReadAnimationXML(strSpriteName, strFrameXMLFile);
        }

        /// <summary>
        /// Default constructor which has no animation
        /// </summary>
        /// <param name="spriteTexture"></param>
        public SpriteAnimation(Texture2D spriteTexture)
        {
            this.Sprite = spriteTexture;
            Animation defaultAnimation = new Animation(false, 0);
            List<Rectangle> lstDefaultFrame = new List<Rectangle>();
            lstDefaultFrame.Add(new Rectangle(0, 0, this.Sprite.Width, this.Sprite.Height));
            defaultAnimation.lstFrames = lstDefaultFrame;
            this.dicAnimations.Add("Default", defaultAnimation);
        }

        private void ReadAnimationXML(string strSpriteName, string strAnimationXMLFile)
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(strAnimationXMLFile);
            XmlElement root = xmlDoc.DocumentElement;
            XmlNode xmlSpriteNode = root.SelectSingleNode("Sprite[Name='" + strSpriteName + "']");
            XmlNode xmlXCoordNode = null;
            XmlNode xmlYCoordNode = null;
            XmlNode xmlWidthNode = null;
            XmlNode xmlHeightNode = null;
            if (xmlSpriteNode != null)
            {
                //Get default frame first
                XmlNode xmlDefaultFrameNode = xmlSpriteNode.SelectSingleNode("DefaultFrame");
                XmlNode xmlDefaultXCoordNode = xmlDefaultFrameNode.SelectSingleNode("X");
                XmlNode xmlDefaultYCoordNode = xmlDefaultFrameNode.SelectSingleNode("Y");
                XmlNode xmlDefaultWidthNode = xmlDefaultFrameNode.SelectSingleNode("Width");
                XmlNode xmlDefaultHeightNode = xmlDefaultFrameNode.SelectSingleNode("Height");
                Rectangle recDefaultFrame = new Rectangle(Convert.ToInt32(xmlDefaultXCoordNode.InnerText), Convert.ToInt32(xmlDefaultYCoordNode.InnerText), Convert.ToInt32(xmlDefaultWidthNode.InnerText), Convert.ToInt32(xmlDefaultHeightNode.InnerText));
                List<Rectangle> lstDefaultFrame = new List<Rectangle>();
                lstDefaultFrame.Add(recDefaultFrame);
                Animation defaultAnimation = new Animation(false, 0);
                defaultAnimation.lstFrames = lstDefaultFrame;
                this.dicAnimations.Add("Default", defaultAnimation);

                XmlNodeList xmlAnimationNodes = xmlSpriteNode.SelectNodes("Animation");
                foreach (XmlNode xmlAnimationNode in xmlAnimationNodes)
                {
                    XmlNode xmlAnimationLabelNode = xmlAnimationNode.SelectSingleNode("AnimationLabel");
                    if (xmlAnimationLabelNode == null)
                    {
                        throw new Exception("Animation node not in correct format");
                    }

                    string strAnimationLabel = xmlAnimationLabelNode.InnerText;

                    bool bRepeat = false;
                    XmlNode xmlRepeatNode = xmlAnimationNode.SelectSingleNode("Repeat");
                    if (xmlRepeatNode != null)
                    {
                        if (xmlRepeatNode.InnerText == "1")
                        {
                            bRepeat = true;
                        }
                        else
                        {
                            bRepeat = false;
                        }
                    }
                    else
                    {
                        bRepeat = false;
                    }

                    int nRepeatitions = 0;
                    XmlNode xmlRepeatitionsNode = xmlAnimationNode.SelectSingleNode("Repeatitions");
                    if (xmlRepeatitionsNode != null)
                    {
                        nRepeatitions = Convert.ToInt32(xmlRepeatitionsNode.InnerText);
                    }
                    else
                    {
                        nRepeatitions = 0;
                    }

                    Animation newAnimation = new Animation(bRepeat, nRepeatitions);

                    List<Rectangle> lstFrameRectangles = new List<Rectangle>();
                    XmlNodeList xmlFrameNodes = xmlAnimationNode.SelectNodes("Frame");
                    foreach (XmlNode xmlFrameNode in xmlFrameNodes)
                    {
                        xmlXCoordNode = xmlFrameNode.SelectSingleNode("X");
                        xmlYCoordNode = xmlFrameNode.SelectSingleNode("Y");
                        xmlWidthNode = xmlFrameNode.SelectSingleNode("Width");
                        xmlHeightNode = xmlFrameNode.SelectSingleNode("Height");

                        if ((xmlXCoordNode == null) || (xmlYCoordNode == null) || (xmlWidthNode == null) || (xmlHeightNode == null))
                        {
                            throw new Exception("Frame node not in correct format");
                        }

                        Rectangle recFrame = new Rectangle();
                        recFrame.X = Convert.ToInt32(xmlXCoordNode.InnerText);
                        recFrame.Y = Convert.ToInt32(xmlYCoordNode.InnerText);
                        recFrame.Width = Convert.ToInt32(xmlWidthNode.InnerText);
                        recFrame.Height = Convert.ToInt32(xmlHeightNode.InnerText);
                        lstFrameRectangles.Add(recFrame);
                    }

                    newAnimation.lstFrames = lstFrameRectangles;

                    this.dicAnimations.Add(strAnimationLabel, newAnimation);
                }

                if ((xmlXCoordNode == null) || (xmlYCoordNode == null) || (xmlWidthNode == null) || (xmlHeightNode == null))
                {
                    throw new Exception("Sprite node not in correct format");
                }
            }
            else
            {
                Animation defaultAnimation = new Animation();
                List<Rectangle> lstDefaultFrame = new List<Rectangle>();
                lstDefaultFrame.Add(new Rectangle(0, 0, this.Sprite.Width, this.Sprite.Height));
                defaultAnimation.lstFrames = lstDefaultFrame;
                this.dicAnimations.Add("Default", defaultAnimation);
            }
        }

        #region Animation
        /// <summary>
        /// Starts the animation with the given label
        /// </summary>
        /// <param name="gameTime"></param>
        /// <param name="strAnimationLabel">Animation to start</param>
        public void StartAnimation(GameTime gameTime, string strAnimationLabel)
        {
            this.bAnimating = true;
            this.nSelectedFrame = 0;
            this.nRepeatitions = 0;
            this.strCurrentAnimationLabel = strAnimationLabel;
            this.dPreviousFrameTime = gameTime.TotalGameTime.TotalSeconds;
        }

        /// <summary>
        /// Pauses the current animation
        /// </summary>
        public void PauseAnimation()
        {
            this.bAnimating = false;
        }

        /// <summary>
        /// Resumes the current animation
        /// </summary>
        public void ResumeAnimation()
        {
            this.bAnimating = true;
        }

        /// <summary>
        /// Stops the current animation and resets to default frame
        /// </summary>
        public void StopAnimation()
        {
            this.bAnimating = false;
            this.nSelectedFrame = 0;
            //Set animation label back to default
            this.strCurrentAnimationLabel = "Default";
        }

        /// <summary>
        /// Continues the animation currently running by incrementing the frame at the correct time
        /// </summary>
        /// <param name="gameTime"></param>
        public void ContinueAnimation(GameTime gameTime)
        {
            if (this.bAnimating)
            {
                //If time to change the frame
                if ((gameTime.TotalGameTime.TotalSeconds - this.dPreviousFrameTime) > (1 / D_FRAMES_PER_SECOND))
                {
                    this.nSelectedFrame++;
                    //If finished with animation
                    if (this.nSelectedFrame >= this.dicAnimations[this.strCurrentAnimationLabel].lstFrames.Count)
                    {
                        //If not repeating animation
                        if (!this.dicAnimations[this.strCurrentAnimationLabel].RepeatAnimation)
                        {
                            this.nSelectedFrame = 0;
                            this.nRepeatitions = 0;
                            this.bAnimating = false;

                            this.strCurrentAnimationLabel = "Default";
                        }
                        //If repeating animation
                        else
                        {
                            //Go back to first frame of animation
                            this.nSelectedFrame = 0;

                            //If not an infinite number of repeatitions
                            if (this.dicAnimations[this.strCurrentAnimationLabel].NumOfRepeatitions != 0)
                            {
                                this.nRepeatitions++;

                                //If finished with repeatitions
                                if (this.nRepeatitions > this.dicAnimations[this.strCurrentAnimationLabel].NumOfRepeatitions)
                                {
                                    this.nSelectedFrame = 0;
                                    this.nRepeatitions = 0;
                                    this.bAnimating = false;

                                    this.strCurrentAnimationLabel = "Default";
                                }
                            }
                        }
                    }
                    else
                    {
                        this.dPreviousFrameTime = gameTime.TotalGameTime.TotalSeconds;
                    }
                }
            }
        }
        #endregion

        #region Draw
        public void Draw(SpriteBatch spriteBatch, Vector2 vPosition)
        {
            spriteBatch.Draw(this.Sprite, vPosition, this.dicAnimations[this.strCurrentAnimationLabel].lstFrames[this.nSelectedFrame], Color.White);
        }

        public void Draw(SpriteBatch spriteBatch, Vector2 vPosition, float fRotation, Vector2 vOrigin)
        {
            spriteBatch.Draw(this.Sprite, vPosition, this.dicAnimations[this.strCurrentAnimationLabel].lstFrames[this.nSelectedFrame], Color.White, fRotation, vOrigin, 1.0f, SpriteEffects.None, 0.0f);
        }

        public void Draw(SpriteBatch spriteBatch, Vector2 vPosition, float fRotation, Vector2 vOrigin, Color color)
        {
            spriteBatch.Draw(this.Sprite, vPosition, this.dicAnimations[this.strCurrentAnimationLabel].lstFrames[this.nSelectedFrame], color, fRotation, vOrigin, 1.0f, SpriteEffects.None, 0.0f);
        }
        #endregion
    }
}
